using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.Mvc.Ajax;
using BulletOnRails4NET.Models;
using System.Reflection;
using System.Linq.Dynamic;
using System.Data.Linq;
using System.Data.Linq.Mapping;
using Suteki.Common.Repositories;

namespace BulletOnRails4NET.Controllers
{
    public static class ControllerHelpers
    {
        public static void AddRuleViolations(this ModelStateDictionary modelState,
        IEnumerable<RuleViolation> errors)
        {
            foreach (RuleViolation issue in errors)
            {
                modelState.AddModelError(issue.PropertyName, issue.ErrorMessage);
           }
        }
        public static void AddRuleViolations(this ModelStateDictionary modelState,
            IEnumerable<RuleViolation> errors, FormCollection formValues)
        {
            foreach (RuleViolation issue in errors)
            {
                modelState.AddModelError(issue.PropertyName, issue.ErrorMessage);
                modelState.SetModelValue(issue.PropertyName, formValues.GetValue(issue.PropertyName));
            }
        }
    }



    public abstract class ScaffoldController<CModelType> : Controller where CModelType : CrudModel
    {
        protected IRepository<CModelType> _db;
        private Type _db_type;
       
        protected int _admin_clubber_id = 1;
        private ConstructorInfo _model_constructor;

        public static JsonResult NotFoundJson = new JsonResult
        {
            Data = new 
            {
                type = "Not Found"
            }
        };
        public static JsonResult OperationSuccesfulJson = new JsonResult
        {
            Data = new
            {
                type = "Operation Succesful"
            }
        };
        //private MethodInfo _db_get_method, 
        //    _db_save_method, 
        //    _db_delete_method;
           //d _db_find_all_method;

        //Associations (belongs to)
        private Dictionary<string, MethodInfo> _associations_list_getters;
        List<AssociationQueryInfo> _associations_info;

        private PropertyInfo[] _crud_model_properties;
        public  GridHelper Grid {get; set;}
        const int _page_size = 5;

        public ScaffoldController()
        {
            _db = (IRepository<CModelType>)RepositoryFactory.GetInstance().GetRepository(typeof(CModelType).Name);
            _db_type = _db.GetType();
            initialize(typeof(CModelType).Name, typeof(CModelType));

        }
        public ScaffoldController(string model_name, IRepository<CModelType> repository)
        {
            _db = repository;
            initialize(typeof(CModelType).Name, typeof(CModelType));
        }

        private void initialize(string model_name, Type model_type)
        {
            _db_type = _db.GetType();
            _model_constructor = model_type.GetConstructor(System.Type.EmptyTypes);
             _crud_model_properties = GetDisplayableModelProperties();
            string controller_name = GetType().Name.Replace("Controller", "");
            Grid = new GridHelper(controller_name, _crud_model_properties, 
                PathResolver.GetSiteRoot() + controller_name + "/GetGridData", 
                GetPageSize(), controller_name,
                PathResolver.GetSiteRoot() + controller_name + "/GetGridMetaData");
            BuildAssociationsListGetters();
            AddCustomFields();
        }

        protected virtual void AddCustomFields()
        {
        }

        //Builds the lists that contain the queries to the "belong-to"
        // associations contained in the CrudModel 
        protected virtual void BuildAssociationsListGetters()
        {
            PropertyInfo[] model_properties = typeof(CModelType).GetProperties();
            object[] info;
            AssociationAttribute assoc_attr;
            _associations_list_getters = new Dictionary<string, MethodInfo>();
            _associations_info = new List<AssociationQueryInfo>();
             foreach (PropertyInfo prop in model_properties)
            {
                //Get the Linq related info
                info = prop.GetCustomAttributes(typeof(AssociationAttribute), true);
                // Check if it is a Linq column property
                if (info.Length == 1)
                {
                    assoc_attr = (AssociationAttribute)info[0];
                    //Check if it is an association column
                    if (assoc_attr.IsForeignKey)
                    {
                       // Repository<CrudModel> assoc_rep = new Repository<CrudModel>();
                        string assoc_class_name = prop.PropertyType.Name;
                        IQueryable<IDLabelPair> possible_assoc_items = (IQueryable<IDLabelPair>)
                            RepositoryFactory.GetInstance().GetRepository(assoc_class_name).GetAllAsIDLabelPairs();
                        _associations_info.Add(new AssociationQueryInfo(assoc_attr.ThisKey, possible_assoc_items));
                    }
                 }
            }
        }


        //Gets the Model's properties that can be edited and shown by the scaffold
        protected virtual PropertyInfo[] GetDisplayableModelProperties()
        {
            PropertyInfo[] model_properties = typeof(CModelType).GetProperties();
            object[] info;
            List<PropertyInfo> db_properties = new List<PropertyInfo>();
            foreach (PropertyInfo prop in model_properties)
            {
                //Get the Linq related info
                info = prop.GetCustomAttributes(typeof(ColumnAttribute), true);
                // Check if it is a Linq column property
                if (info.Length == 1)
                {
                    db_properties.Add(prop);
                }
            }
            return db_properties.ToArray();
        }

        public virtual ActionResult Index()
        {
            return RedirectToAction("List");
        }
        
        [OutputCache(Duration = 3600*24, VaryByParam = "none")]
        public virtual ActionResult List()
        {
            return View(Grid);
        }

        public virtual ActionResult GetGridMetaData()
        {
            return Grid.grid_meta_data;
        }


        /// <summary>
        /// Gets a dictionary indexed by foreign key name wich each entry
        ///  contains the array of all possible values for that associantion.
        ///  The values are pulled by querying all the records in the associated table
        /// </summary>
        /// <returns>The dictionary converted to JSON data</returns>
        public virtual ActionResult GetAssociationsPossibleValues()
        {
            Dictionary<string, List<IDLabelPair>> assoc_possible_values = new Dictionary<string, List<IDLabelPair>>();
            foreach (AssociationQueryInfo assoc_info in _associations_info)
            {
                assoc_possible_values.Add(assoc_info.FkColumnName,
                    assoc_info.AssociationsListQuery.ToList());
            }
            return new JsonResult
            {
                Data = assoc_possible_values
            };
        }

        public virtual ActionResult GetGridData()
        {

            //int page_number = 0;
            string sort_field = Request["sidx"];
            string sort_order = Request["sord"];
            if (sort_order == "desc")
                sort_order = "descending";
            else if (sort_order == "asc")
                sort_order = "";
            int page_number = Convert.ToInt32(Request["page"]);
            if (page_number > 0)
                page_number--;
            else
                page_number = 0;
          
            IQueryable<CModelType> clubbers = (IQueryable<CModelType>) _db.GetAll() ;

            var rows = (clubbers.OrderBy(sort_field + " " + sort_order).Skip(page_number * GetPageSize())
.Take(GetPageSize()).Select(c =>
        
        new
            {

                id = c.GetId(),

                cell = c.GetPropertyValuesAsString(_crud_model_properties)

            }
    )).ToArray();

            int num_clubbers = clubbers.Count();
            return new JsonResult
            {
                Data = new
                {
                    page = page_number + 1,
                    records = rows.Length,
                    rows,
                    total = (int)Math.Ceiling(num_clubbers / (double)GetPageSize())
                }
            };

            
        }

        public virtual IQueryable<CModelType> GetRecordsForListView()
        {
            return null;
        }

        public virtual int GetPageSize()
        {
            return _page_size;
        }
        //
        // GET: /Clubber/Details/5

        public virtual ActionResult Details(int id)
        {
            CrudModel clubber = (CrudModel)_db.GetById(id);
            if (clubber == null)
                return View("NotFound");
            return View(new CrudViewModel(clubber, GetDisplayableModelProperties()));
        }

        [OutputCache(Duration = 3600 * 24, VaryByParam = "none")]
        public virtual ActionResult Create()
        {
            CrudModel new_clubber = (CrudModel)_model_constructor.Invoke(null);
            return View(new CrudViewModel(new_clubber, GetDisplayableModelProperties(), _associations_info));
        }

        protected virtual List<RuleViolation> BeforeSaveCreate(FormCollection formValues,
            CrudModel model_to_save)
        {
            return null;
        }

        [AcceptVerbs(HttpVerbs.Post)]
        public virtual ActionResult Create(FormCollection formValues)
        {
            CrudModel new_clubber = (CrudModel)_model_constructor.Invoke(null);
            List<RuleViolation> violations = FormHelper.PopulateProperties(new_clubber, formValues);
            if (violations.Count == 0)
            {   
                try
                {
                    //UpdateModel(new_clubber);
                    _db.InsertOnSubmit(new_clubber as CModelType);
                    List<RuleViolation> extra_violations = BeforeSaveCreate(formValues, new_clubber);
                    if (extra_violations == null || extra_violations.Count == 0)
                    {
                        _db.SubmitChanges();
                        return OperationSuccesfulJson;
                    }
                    else
                    {
                        violations.AddRange(extra_violations);
                    }
                }
                catch (RuleViolationException e)
                {
                    //ModelState.AddRuleViolations(e.RulesViolations, formValues);
                    violations.AddRange(e.RulesViolations);
                }
            }
            else {
                ModelState.AddRuleViolations(violations, formValues);
            }
            return new JsonResult
            {
                Data = new
                {
                    type = "Error",
                    violations = violations
                }
            };
        }

        ////
        //// GET: /Exercises/Edit/5
        //[Authorize(Roles = "admin")]
        public virtual JsonResult Edit(int id)
        {
            CrudModel clubber = (CrudModel)_db.GetById(id);
            if (clubber == null)
                return NotFoundJson;
            return OperationSuccesfulJson;
        } 

        ////
        // POST: /Exercises/Edit/5
        [AcceptVerbs(HttpVerbs.Post)]
        public virtual JsonResult Edit(int id, FormCollection formValues)
        {
            CrudModel clubber = (CrudModel)_db.GetById(id); 
            if (clubber == null)
                return NotFoundJson;
            List<RuleViolation> violations = FormHelper.PopulateProperties(clubber, formValues);
            if (violations.Count == 0)
            {
                try
                {
                    _db.SubmitChanges();
                    return OperationSuccesfulJson;
                }
                catch (RuleViolationException e)
                {
                    violations.AddRange(e.RulesViolations);
                }
            }
            else
            {
                ModelState.AddRuleViolations(violations, formValues);
            }
            return new JsonResult
            {
                Data = new
                {
                    type = "Error",
                    violations = violations
                }
            };
        }

        public virtual ActionResult EditMultiple(FormCollection formValues)
        {

            FormCollection filled_fields = new FormCollection();
            foreach (String field_name in formValues.Keys)
            {
                if (!formValues[field_name].IsBlank())
                    filled_fields[field_name] = formValues[field_name];
            }

            string[] ids_to_change_arr = formValues["ids_to_change"].Split(',');
            int id;
            List<object> results = new List<object>();
            List<RuleViolation> violations;

            foreach (string str_id in ids_to_change_arr)
            {
                if (int.TryParse(str_id, out id))
                {

                    CrudModel record = (CrudModel)_db.GetById(id);
                    if (record == null)
                    {
                        RuleViolation[] not_found_violation = {new RuleViolation("ID Not Found")};
                        results.Add(new
                        {
                            record_id = id,
                            result = not_found_violation
                        });
                    }
                    else
                    {
                        violations = FormHelper.PopulateProperties(record, filled_fields);
                        results.Add(new
                        {
                            record_id = id,
                            result = violations
                        });
                    }

                }
            }
            _db.SubmitChanges();
            return new JsonResult
            {
                Data = new
                {
                    type = "Batch Results",
                    violations = results
                }
            };
        }

        [AcceptVerbs(HttpVerbs.Post)]
        public virtual ActionResult Delete()
        {
            string ids_to_delete = Request.Form["ids_to_delete"];
            string[] ids_to_delete_arr = ids_to_delete.Split(',');
            int id;

            List<String> labels_of_records_deleted = new List<String>();
            foreach (string str_id in ids_to_delete_arr)
            {
                if (int.TryParse(str_id, out id))
                {
                    CrudModel model = (CrudModel)_db.GetById(id);
                    if (model != null)
                    {
                        _db.DeleteOnSubmit(model as CModelType);
                        labels_of_records_deleted.Add(model.GetAsLabel());
                    }
                }
            }
            _db.SubmitChanges();
            return new JsonResult
            {
                Data = new
                {
                    type = "Batch Results",
                    deleted_records = labels_of_records_deleted
                }
            };
        }


    }
}
